/*
 * pack.c
 *
 *  Created on: Sep 6, 2020
 *      Author: daniel
 */

#include "pack.h"
#include "encrypt/chachapoly.h"
#include "log.h"

#include <time.h>

#define NODE_ID		0x01
#define HOST_ID		0x00

const uint16_t				wind_dir_correct = 72;		// 方向修正数据（该值为当前风向标指向的正北时的值）

// x 分钟的风力风向数据，用来计算阵风
#define WIND_GUST_TIME		5 * 60						// 5分钟内的最大阵风计算
typedef struct {
	time_t		t;
	uint16_t	speed;
	uint16_t	dir;
} WIND_INFO;

static WIND_INFO	windInfos[WIND_GUST_TIME] = {};
static int			windInfos_index = 0;

extern int8_t	rssi, snr;

uint8_t PayloadUnpack(const uint8_t* buffer, uint8_t buffersize, uint8_t no, uint8_t* sendBuffer);

void payload_aead_encrypt(uint8_t* buffer, uint8_t bufsize, uint8_t* random_p, uint8_t* tag);
bool payload_aead_decrypt(uint8_t* buffer, uint8_t bufsize, uint8_t* random_p, uint8_t* tag);

// LoRA Payload编码格式
/*
 * byte		说明
 * 1:		发送节点编号（主机永远是0。发送节点永远是自己的编号）
 * 2：		接收节点编号
 * 3：		命令（0：数据；   1：请求Firmware；   2：数据ACK；   3：Firmware data）；   4：请求配置数据；    5：配置数据响应
 * 4：		序号（0～255)
 * 5-(5+8):	随机数（8bytes）
 * 14-（14+16）：tag（poly1305的计算结果）
 * 27-:		Payload数据
 *
 * Payload说明：
 * 数据和数据的ACK全部使用chacha20-poly1305加密（实际也是同时实现数据校验）
 *
 * 数据的ACK Payload：
 * 1：0，None； 1，有升级数据； 2，有配置数据
 *
 * Firmware请求Payload格式：
 * 1，2：	16bit的位置序号。（每个包64字节），支持断点续传，但是只能按顺序传。
 *
 * Firmware数据Payload格式：
 * 不携带位置序号（或者称为，不响应位置序号）。对于请求的响应，只对“包头的序号”
 */

// 主机端接口
uint8_t packDataAck(const WS_DATA_ACK* data, uint8_t cmd, uint8_t no, uint8_t* buffer, uint8_t buffersize)
{	uint8_t		packSize = 0;
	uint8_t		payloadSize = 0;
	uint8_t*	random_pos;
	uint8_t*	tag_pos;
	uint8_t*	payload_pos;

	// send id
	buffer[packSize++] = HOST_ID;

	// receive id
	buffer[packSize++] = NODE_ID;

	// random (8bytes)
	random_pos = buffer + packSize;
	packSize += 8;

	// poly1305 (16bytes)
	tag_pos = buffer + packSize;
	packSize += POLY1305_BLOCK_SIZE;

	payload_pos = buffer + packSize;
	// cmd
	buffer[packSize++] = cmd;

	// pack no
	buffer[packSize++] = no;

	// payload encode
	buffer[packSize++] = data->data;
	payloadSize += 1;

	if (data->data == FIRMWARE_INFO_NEW) {
		// Add firmware info
		uint32_t	ver = data->firmware_info.firmwareVer;
		buffer[packSize++] = (uint8_t)((ver >> 24 ) & 0xFF);
		buffer[packSize++] = (uint8_t)((ver >> 16 ) & 0xFF);
		buffer[packSize++] = (uint8_t)((ver >> 8 ) & 0xFF);
		buffer[packSize++] = (uint8_t)(ver & 0xFF);

		uint32_t	len = data->firmware_info.firmwareSize;
		buffer[packSize++] = (uint8_t)((len >> 24) & 0xFF);
		buffer[packSize++] = (uint8_t)((len >> 16) & 0xFF);
		buffer[packSize++] = (uint8_t)((len >> 8) & 0xFF);
		buffer[packSize++] = (uint8_t)(len & 0xFF);

		memcpy(buffer + packSize, data->firmware_info.tag, 16);
		packSize += 16;

		payloadSize += 4 + 4 + 16;
	}

	// payload encrypt
	// 流式加密，长度一致（连同cmd和no）
	payload_aead_encrypt(payload_pos, payloadSize + 2, random_pos, tag_pos);

	return packSize;
}

void WindGust(WS_DATA* data)
{
	time_t		now = time(NULL);
	time_t		old = now - WIND_GUST_TIME;
	int			index = windInfos_index;

	// 写入最新数据
	WIND_INFO* pInfo = windInfos + windInfos_index;
	pInfo->t = now;
	pInfo->speed = data->wind_speed;
	pInfo->dir = data->wind_dir;

	windInfos_index++;
	if ( windInfos_index >= WIND_GUST_TIME) {
		windInfos_index = 0;
	}

	data->wind_gust_dir = 0;
	data->wind_gust_speed = 0;

	// 检索过去这段时间的最大风速和对应的风向
	for (int i = 0; i < WIND_GUST_TIME; i++) {
		pInfo = windInfos + index;
		if (pInfo->t < old) {
			break;
		}

		if (pInfo->speed > data->wind_gust_speed) {
			data->wind_gust_speed = pInfo->speed;
			data->wind_gust_dir = pInfo->dir;
		}

		index--;
		if (index < 0) {
			index = WIND_GUST_TIME - 1;
		}
	}
}

uint8_t PayloadUnpack(const uint8_t* buffer, uint8_t buffersize, uint8_t no, uint8_t* sendBuffer)
{
	uint8_t		pos = 0;
	WS_DATA		data;

	// No
	data.no = no;

	data.rssi = rssi;
	data.snr = snr;

	// Mask
	if (buffersize < pos + 1)
	{
		return 0;
	}
	data.dataMask = buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.temperature = buffer[pos++] << 8;
	data.temperature += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.humidity = buffer[pos++] << 8;
	data.humidity += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.wind_dir = buffer[pos++] << 8;
	data.wind_dir += buffer[pos++];

	data.wind_dir += (360 - wind_dir_correct);
	data.wind_dir %= 360; 

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.wind_speed = buffer[pos++] << 8;
	data.wind_speed += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.pressure = buffer[pos++] << 8;
	data.pressure += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.solar_radiation = buffer[pos++] << 8;
	data.solar_radiation += buffer[pos++];

	if (buffersize < pos + 4)
	{
		return 0;
	}
	data.illumination = buffer[pos++] << 24;
	data.illumination += buffer[pos++] << 16;
	data.illumination += buffer[pos++] << 8;
	data.illumination += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.uv_index = buffer[pos++] << 8;
	data.uv_index += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.uva_value = buffer[pos++] << 8;
	data.uva_value += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.uvb_value = buffer[pos++] << 8;
	data.uvb_value += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.battery_voltage = buffer[pos++] << 8;
	data.battery_voltage += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.battery_current = buffer[pos++] << 8;
	data.battery_current += buffer[pos++];

	//#define DATA_RAIN
	if (buffersize < pos + 4)
	{
		return 0;
	}
	data.rain =  buffer[pos++] << 24;
	data.rain += buffer[pos++] << 16;
	data.rain += buffer[pos++] << 8;
	data.rain += buffer[pos++];

	//#define DATA_LIGHTNING	(0x1000)
	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.lightning_dist = buffer[pos++] << 8;
	data.lightning_dist += buffer[pos++];

	if (buffersize < pos + 2)
	{
		return 0;
	}
	data.solar_battery = buffer[pos++] << 8;
	data.solar_battery += buffer[pos++];

	if (buffersize < pos + 4)
	{
		return 0;
	}
	data.version =  buffer[pos++] << 24;
	data.version += buffer[pos++] << 16;
	data.version += buffer[pos++] << 8;
	data.version += buffer[pos++];

	//#define DATA_FIRMWARE
	data.firmware = FIRMWARE_NONE;

	if (data.dataMask & DATA_FIRMWARE)
	{
		if (buffersize < pos + 1)
		{
			return 0;
		}
		data.firmware = buffer[pos++];
	}

	// 分析阵风数据
	WindGust(&data);

	// 调用数据处理函数
	return cmd_Data(&data, no, sendBuffer);
}

uint8_t PayloadFirmwareGet(const uint8_t* buffer, uint8_t buffersize, uint8_t no, uint8_t* sendBuffer)
{
	uint32_t		pos;

	if (buffersize < 4) {
		return 0;
	}

	pos = buffer[0] << 24;
	pos += buffer[1] << 16;
	pos += buffer[2] << 8;
	pos += buffer[3];

	return cmd_GetFirmware(pos, no, sendBuffer);
}

uint8_t packFirmwareData(const uint8_t* data, uint8_t dataSize, uint8_t no, uint8_t* buffer, uint8_t buffersize)
{
	uint8_t		packSize = 0;
	uint8_t*	random_pos;
	uint8_t*	tag_pos;
	uint8_t*	payload_pos;

	// send id
	buffer[packSize++] = HOST_ID;

	// receive id
	buffer[packSize++] = NODE_ID;

	// random (8bytes)
	random_pos = buffer + packSize;
	packSize += 8;

	// poly1305 (16bytes)
	tag_pos = buffer + packSize;
	packSize += POLY1305_BLOCK_SIZE;

	payload_pos = buffer + packSize;
	// cmd
	buffer[packSize++] = CMD_FIRMWARE_DATA;

	// pack no
	buffer[packSize++] = no;

	// payload encode
	memcpy(buffer + packSize, data, dataSize);

	// payload encrypt
	// 流式加密，长度一致（连同cmd和no）
	payload_aead_encrypt(payload_pos, dataSize + 2, random_pos, tag_pos);

	packSize += dataSize;

	return packSize;
}

// 紧急状态，PWS只会发送Beaon，Agent返回和常规返回的数据类似，有重启和固件版本信息
uint8_t PayloadBeacon(const uint8_t* buffer, uint8_t buffersize, uint8_t no, uint8_t* sendBuffer)
{
	uint8_t				pos = 0;
	WS_LORA_STATUS		data;

	// No
	data.no = no;

	// RSSI
	if (buffersize < pos + 1)
	{
		return 0;
	}
	data.rssi = (int8_t)buffer[pos++];

	// SNR
	if (buffersize < pos + 1)
	{
		return 0;
	}
	data.snr = (int8_t)buffer[pos++];

	return cmd_BeaconAck(&data, no, sendBuffer);
}

uint8_t packBeaconAck(const uint8_t* data, uint8_t dataSize, uint8_t no, uint8_t* buffer, uint8_t buffersize)
{
	// 可以重用packDataAck（把cmd带入）
	return 0;
}

// 输入接收到的数据，解密。
// 返回准备回应的缓冲区和长度
// no是请求响应的编号
// 升级数据的包长度固定（暂定64字节），最后一包不固定。
uint8_t unpackData(uint8_t* buffer, uint8_t bufferSize, uint8_t* no, uint8_t* sendBuffer)
{
	uint8_t		packSize = 0;
	uint8_t		payload_size;
	uint8_t*	payload_pos;
	uint8_t*	random_pos;
	uint8_t*	tag_pos;
	bool		decrypt_ok;

	// 至少 1 + 1 + 8 + 16 + 2字节
	if (bufferSize <= ( 1 + 1 + 8 + 16 + 2))
	{
		return 0;
	}

	// 判断是否节点发出，是否主机接收
	if ( (buffer[0] == HOST_ID) || (buffer[1] != HOST_ID) )
	{
		return 0;
	}

	random_pos = buffer + 2;
	tag_pos = buffer + 2 + 8;
	payload_pos = buffer + 2 + 8 + 16;
	payload_size = bufferSize - 2 - 8 - 16;
	decrypt_ok = payload_aead_decrypt(payload_pos, payload_size, random_pos, tag_pos);

	if (!decrypt_ok)
	{
		return 0;
	}

	log_print(LOG_DEBUG, "Decrypt %d bytes", payload_size);

	// 发送序号
	*no = payload_pos[1];

	// 处理数据
	switch (payload_pos[0])
	{
	case CMD_DATA:
		packSize = PayloadUnpack(payload_pos + 2, payload_size - 2, *no, sendBuffer);
		break;

	case CMD_FIRMWARE_GET:
		packSize = PayloadFirmwareGet(payload_pos + 2, payload_size - 2, *no, sendBuffer);
		break;

	case CMD_BEACON:
		packSize = PayloadBeacon(payload_pos + 2, payload_size - 2, *no, sendBuffer);
		break;

	default:
		break;
	}

	return packSize;
}

static struct chachapoly_ctx ctx;

const unsigned char key[32] = {
		0xf9, 0xd7, 0xde, 0xe9, 0xa9, 0xcb, 0xbd, 0xec, 0xbf, 0xf8, 0x8a, 0xa7, 0x78, 0xb5, 0x99, 0xb2,
		0x8c, 0x1a, 0xdf, 0xd6, 0x26, 0x28, 0x92, 0x3a, 0xae, 0xe4, 0x76, 0xc5, 0xec, 0x2f, 0x38, 0x1c
};

const unsigned char ad[12] = {
		0xce, 0x86, 0x81, 0x95, 0x3d, 0x44, 0x63, 0x51, 0xac, 0xb8, 0x45, 0x39
};

void payload_aead_init()
{
	chachapoly_init(&ctx, key, 256);
}

/*
 * buffer：明文数据，输入。同时输出加密数据
 * bufsize：数据长度
 * random：随机数输出（8bytes）
 * tag：auth tag输出（16bytes）
 */
void payload_aead_encrypt(uint8_t* buffer, uint8_t bufsize, uint8_t* random_p, uint8_t* tag)
{
	uint32_t	r;
#ifdef __PWS__
	uint32_t	seed = HAL_GetTick();
#else
	uint32_t	seed = time(NULL);
#endif

	// 生成随机数
	srandom(seed);
	r = (uint32_t)random();
	random_p[0] = r & 0xFF;
	random_p[1] = (r >> 8) & 0xFF;
	random_p[2] = (r >> 16) & 0xFF;

	r = (uint32_t)random();
	random_p[3] = r & 0xFF;
	random_p[4] = (r >> 8) & 0xFF;
	random_p[5] = (r >> 16) & 0xFF;

	r = (uint32_t)random();
	random_p[6] = r & 0xFF;
	random_p[7] = (r >> 8) & 0xFF;

	// 加密(todo:确认是否可以公用in out指针）
	chachapoly_crypt(&ctx, random_p, ad, 12, buffer, bufsize, buffer, tag, 16, 1);
}

bool payload_aead_decrypt(uint8_t* buffer, uint8_t bufsize, uint8_t* random_p, uint8_t* tag)
{
	int	ret = 0;

	ret = chachapoly_crypt(&ctx, random_p, ad, 12, buffer, bufsize, buffer, tag, 16, 0);

	return (ret == 0);
}
